import { IllBlock, Ill } from './ill.js';
import { LivingMaterial, MedicalMaterial, ProtectiveMaterial } from './material.js';
import { Help } from './help.js';

// 工人类
class WorkerGroup {
    constructor(total) {
        this.workable = total;
        this.total = total;
    }

    // 根据发病率更新可用工人数量
    updateWorkable(morbidity) {
        this.workable = Math.floor((1 - morbidity) * this.total);
    }

    // 增加工人
    add(num) {
        this.workable += num;
        this.total += num;
    }

    // 减少工人
    sub(num) {
        this.workable = Math.max(0, this.workable - num);
        this.total = Math.max(0, this.total - num);
    }
}

// 距离结构体
function createDistStru(dist, city) {
    return { dist, c: city };
}

// 交通枢纽状态枚举
const HubStatus = {
    common: 0,
    check: 1,
    close: 2,
    specialization: 3
};

// 交通枢纽类
class Hub {
    constructor(name, status = HubStatus.common, isAirport = false) {
        this.name = name;
        this.s = status;
        this.isAirport = isAirport;
    }

    // 是否可用
    isUsable() {
        return this.s === HubStatus.common || this.s === HubStatus.check;
    }

    // 是否在检查状态
    isCheck() {
        return this.s === HubStatus.check;
    }
}

// 门诊状态枚举
const OutpatientStat = {
    order: 0,     // 正常
    runon: 1,     // 恐慌
    avoid: 2,     // 医疗资源不足/失去秩序
    collapse: 3   // 崩溃
};

// 城市类
class City {
    constructor(province, name, level, population, instituteNum, x, y, allHub = []) {
        // 传播参数
        this.avgContactNum = 0;
        this.permanentPopulationProp = 0;
        // 医疗参数
        this.patientTreatRate = 2.0 / 3; // 病人数*k=处理病人所需医生数
        this.outpatientTreatRate = 1.0 / 10; // 门诊人数*k=处理门诊所需医生数
        // 秩序参数
        this.outpatientMultiple = [4, 15, 1.0 / 2, 0]; // 正常、恐慌、医疗资源不足/失去秩序
        this.productionEfficiency = [1.01, 2.0 / 3, 1.0 / 4, 0]; // 正常、恐慌、失去秩序、崩溃
        this.initDiagnosticRate = 0.6; // 初始值为未研究状况下的门诊诊断

        // 运输参数
        this.spreadRange = 0; // 交通范围

        // 基本信息
        this.province = province;
        this.name = name;
        this.level = level;
        // 疾病区块
        this.permanentPopulation = 0;
        this.hospitalBlock = new IllBlock(0);
        this.lifeBlock = new IllBlock(population);
        this.illness = new Ill(true);
        // 工作人员
        this.newsNum = new WorkerGroup(Math.floor((population * this.getStaffProp()) / 2));
        this.medicalNum = new WorkerGroup(Math.floor((population * this.getStaffProp()) / 3));
        this.securityNum = new WorkerGroup(Math.floor(population * this.getStaffProp()));
        this.trafficNum = new WorkerGroup(Math.floor(population * this.getStaffProp()));
        this.researchNum = new WorkerGroup(instituteNum * 150); // 设一个机构150人（生物系）
        // 设施
        this.instituteNum = instituteNum;
        // 坐标
        this.x = x;
        this.y = y;
        // 物资
        this.livingMaterial = new LivingMaterial(population);
        this.medicalMaterial = new MedicalMaterial(population, this.livingMaterial);
        this.protectiveMaterial = new ProtectiveMaterial(this.medicalNum.total, this.livingMaterial);
        // 政治
        this.panic = 0;
        this.know = 0.2; // 对该传染病了解（谣言也算了解），决定发病愿意就诊人数
        // 其它城市距离
        this.allDist = [];
        // 交通
        this.allHub = [];
        this.internalHub = new Hub("城市内部交通");
        this.generalExternalHub = new Hub("普通外部交通");
        // 医疗参数
        this.researchProgress = 0; // 这是一个比例，增加的就是增加1+这个，减少的就是直接乘

        // 初始化与城市级别相关的参数
        // 传播参数
        const avgContactNum = [50, 45, 40, 30, 25, 15];
        const permanentPopulationProp = [0.57, 0.6, 0.7, 0.8, 0.9, 0.95];
        // 运输参数
        const spreadRange = [2, 2, 2, 2, 2, 2];
        // 初始化对象参数
        const sub = level - 1;
        this.avgContactNum = avgContactNum[sub];
        this.permanentPopulationProp = permanentPopulationProp[sub];
        this.spreadRange = spreadRange[sub];
        this.permanentPopulation = Math.floor(population * this.permanentPopulationProp);
        // 交通
        for (const i of allHub) {
            this.allHub.push(new Hub(i));
        }
    }

    // 获取工作人员比例
    getStaffProp() {
        if (this.level === 1) return 0.02;
        else if (this.level === 2) return 0.018;
        else if (this.level === 3) return 0.016;
        else if (this.level === 4) return 0.014;
        else if (this.level === 5) return 0.012;
        else return 0.01;
    }

    // 打印状态（调试用）
    print() {
        console.log(`-------${this.name}-------`);
        console.log("-life-");
        this.lifeBlock.print();
        console.log("-hospital-");
        this.hospitalBlock.print();
        console.log("-ill-");
        this.illness.print();
        console.log(`newsNum: ${this.newsNum.workable}`);
        console.log(`medicalNum: ${this.medicalNum.workable}`);
        console.log(`securityNum: ${this.securityNum.workable}`);
        console.log(`trafficNum: ${this.trafficNum.workable}`);
        console.log(`researchNum: ${this.researchNum.workable}`);
        console.log("[finished]");
        console.log("-livingMaterial-");
        this.livingMaterial.print();
        console.log("-medicalMaterial-");
        this.medicalMaterial.print();
        console.log("-protectiveMaterial-");
        this.protectiveMaterial.print();
        console.log(`panic: ${this.panic}`);
        console.log(`know: ${this.know}`);
    }

    // 获取与另一城市的距离
    getDist(city) {
        return Math.pow(this.x - city.x, 2) + Math.pow(this.y - city.y, 2);
    }

    // 获取与另一城市的距离排名
    getDistRank(city) {
        for (let i = 0; i < this.allDist.length; i++) {
            if (this.allDist[i].c.name === city.name) {
                return i + 1;
            }
        }
        return 0;
    }

    // 获取秩序状态
    getOrderStat() {
        if (this.panic > 0.9) return OutpatientStat.collapse;
        else if (this.panic > 0.85) return OutpatientStat.avoid;
        else if (this.isMedicalDeterioration()) return OutpatientStat.avoid;
        else if (this.panic >= 0.45) return OutpatientStat.runon;
        else return OutpatientStat.order;
    }

    // 医疗是否恶化
    isMedicalDeterioration(externalPerspective = true) {
        const result = !this.protectiveMaterial.normal || !this.medicalMaterial.normal;
        if (externalPerspective) { // 外部视角包括床位缺少的问题
            return result || this.lifeBlock.getKnowInfected() !== 0;
        } else {
            return result;
        }
    }

    // 获取门诊人数
    getOutpatients() {
        // realNum: 症状导致想去门诊的（未潜伏期）病人数量（未潜伏期病人数 × 知晓率）
        const realNum = Math.floor(this.lifeBlock.getUnincubation() * this.know);
        console.log('想去门诊的人数：' + this.lifeBlock.getUnincubation() + ' -> ' + realNum);
        // baseNum: 想去所在地门诊的人数（受城市秩序影响，秩序不同，门诊倍数不同。一般恐慌时倍数是大于1的）
        const baseNum = Math.floor(realNum * this.outpatientMultiple[this.getOrderStat()]);
        // realNumWithRand: 添加随机波动后因症状想去门诊人数（±20%波动）
        const realNumWithRand = Math.floor(Help.randJump(realNum, 0.2));
        // baseNumWithRand: 添加随机波动后的想去所在地门诊总人数（±30%波动）
        const baseNumWithRand = Math.floor(Help.randJump(baseNum, 0.3));
        // 随机之后的数值要约束一下大小关系，不能倒挂：活人数<想去所在地门诊人数(real)（恐慌时）<症状导致想去门诊人数(base)
        // finalBaseNum被活人数限制
        const finalBaseNum = Math.min(baseNumWithRand, this.lifeBlock.getAlive());
        // finalRealNum被base限制
        const finalRealNum = Math.min(realNumWithRand, finalBaseNum);
        return [finalBaseNum, finalRealNum];
    }

    // 处理门诊病人
    treatOutpatient() {
        // outpatients: 实际来所在地门诊的人数（恐慌时是倍数关系）
        // realUnincubation: 因症状想来门诊的人数（真实病人）
        const [outpatients, realUnincubation] = this.getOutpatients();
        console.log('前往门诊总人数：' + outpatients + ' 真实病人数量：' + realUnincubation);
        
        // 计算医疗资源限制
        // receptivePatients: 医院最大可接诊人数（根据医疗人员数量和每人可处理的门诊比例计算）
        const receptivePatients = Math.floor(this.medicalNum.workable / this.outpatientTreatRate);
        // actualOutpatients: 可接诊的病人总量（以来医院的人为基数，受限于医疗资源）
        const actualOutpatients = Math.min(outpatients, receptivePatients);
        // actualRealUnincubation: 这里计算的是被接诊的真实病人数量
        // 有一点小问题，如果用min这么算的话，相当于真实病人一定以最高优先级被接诊。但实际上真实病人被接诊的概率会被恐慌过来的没病人稀释的
        const actualRealUnincubation = Math.min(realUnincubation, actualOutpatients);
        
        // 计算接诊这些人中的潜伏期病人数量（realUnincubation里因恐慌多过来的那部分人里有潜伏期的）
        const totalInfected = this.lifeBlock.getInfected(); //生活区域中的总感染人数
        // incubationProp: 过来医院的病人里潜伏期比例（生活区潜伏率/2）
        const incubationProp = totalInfected > 0 ? this.lifeBlock.getIncubation() / (totalInfected * 2) : 0;
        // realIncubation: 按比例计算的门诊中潜伏期病人数量，基数：总接诊人数-接诊人数里人症状的（剩下那些因恐慌过来的无症状人，里面才可能有潜伏期的）
        const realIncubation = Math.floor((actualOutpatients - actualRealUnincubation) * incubationProp);
        
        // 计算诊断数
        // diagnosticRate: 当前的诊断率（受研究进度影响）
        const diagnosticRate = this.WeightingResearchProgress(true, this.initDiagnosticRate, 0.1);
        // incubationDiagnosisNum: 成功诊断的潜伏期病人数量
        const incubationDiagnosisNum = Math.floor(realIncubation * diagnosticRate);
        // unincubationDiagnosisNum: 成功诊断的未潜伏期病人数量
        const unincubationDiagnosisNum = Math.floor(actualRealUnincubation * diagnosticRate);
        console.log('诊断率：' + diagnosticRate + ' realIncubation：' + realIncubation + ' actualRealUnincubation' + actualRealUnincubation);
        console.log('诊断数：' + incubationDiagnosisNum + ' ' + unincubationDiagnosisNum);

        // 将病人移入医院的内部函数
        // num: 需要转移的病人数量
        // isIncubation: 是否为潜伏期病人
        const transferToHospital = (num, isIncubation) => {
            const remainBed = this.getRemainingBeds();
            console.log('需求量：' + num + ' 床位数量：' + remainBed);
            let actualNum = num; //还不知道能装多少人，先设为需要入院的总人数
            
            if (num > remainBed) { //剩余床位不够
                const other = num - remainBed; //无法入院病人数量
                this.lifeBlock.addKnowInfected(other); //将无法住院的病人（还在生活区）标记为已知感染者
                actualNum = remainBed; //能装多少装多少，把床位占满
            }
            console.log('将' + actualNum + '人移入医院');
            if (actualNum <= 0) {
                return; //没有要移的人，所以返回
            }
            
            // 根据病人类型（潜伏期/未潜伏期）进行人数转移
            if (isIncubation) {
                this.lifeBlock.subIncubation(actualNum);
                this.hospitalBlock.addIncubation(actualNum);
            } else {
                this.lifeBlock.subUnincubation(actualNum);
                this.hospitalBlock.addUnincubation(actualNum);
            }
            
            // 向医院添加已知感染者标记（KnowInfected是包含在Unincubation里面的）
            this.hospitalBlock.addKnowInfected(actualNum);
            // 从生活区域总人口中减去转移的人数（IllBlock.total是一个单独的变量，各种类型病人分类是包含在它里面的，所以两个地方数值都要改）
            this.lifeBlock.subTotal(actualNum);
            
            // 处理生活区域的已知感染者数量
            // 生活区内部KnowInfected是不会加的，lifeBlock.KnowInfected不为0是因为上面写的，无法住院的病人还在生活区并被标记为KnowInfected
            const knowInfected = this.lifeBlock.getKnowInfected();
            if (knowInfected < actualNum) {
                // 如果已知感染者少于转移人数，则减去全部已知感染者
                this.lifeBlock.subKnowInfected(knowInfected);
            } else {
                // 否则减去相应数量的已知感染者
                this.lifeBlock.subKnowInfected(actualNum);
            }
            
            // 向医院总人口中添加转移的人数
            this.hospitalBlock.addTotal(actualNum);
        };

        transferToHospital(incubationDiagnosisNum, true);
        transferToHospital(unincubationDiagnosisNum, false);
    }

    // 结算工作人员损失
    settlementWorkersLoss() {
        const lifeMorbidity = this.lifeBlock.getMorbidity();
        
        if (!this.isMedicalDeterioration(false)) {
            this.medicalNum.updateWorkable(lifeMorbidity);
            this.researchNum.updateWorkable(lifeMorbidity);
        } else { // 医疗资源不足时，医护人员死亡率等于医院区块死亡率
            const hospitalMorbidity = this.hospitalBlock.getMorbidity();
            this.medicalNum.updateWorkable(hospitalMorbidity);
            this.researchNum.updateWorkable(hospitalMorbidity);
        }
        
        this.newsNum.updateWorkable(lifeMorbidity);
        this.securityNum.updateWorkable(lifeMorbidity);
        this.trafficNum.updateWorkable(lifeMorbidity);
    }

    // 获取存活总人数
    getAlive() {
        return this.lifeBlock.getAlive() + this.hospitalBlock.getAlive();
    }

    // 更新城市状态（每日调用）
    update() {
        // 资源更新
        this.livingMaterial.updateSub(this.getAlive());
        
        if (!this.livingMaterial.normal) {
            this.lifeBlock.propDecrease(this.livingMaterial.getFamineProp());
            this.hospitalBlock.propDecrease(this.livingMaterial.getFamineProp());
            this.settlementWorkersLoss();
        }
        
        this.livingMaterial.updateAdd(this.getAlive());
        
        const [outpatients] = this.getOutpatients();
        this.protectiveMaterial.updateSub(
            this.hospitalBlock, 
            outpatients, 
            this.medicalNum.workable, 
            this.patientTreatRate, 
            this.outpatientTreatRate
        );
        this.protectiveMaterial.updateAdd(this.medicalNum.total);
        
        this.medicalMaterial.updateSub(
            this.hospitalBlock, 
            this.patientTreatRate, 
            this.medicalNum.workable
        );
        this.medicalMaterial.updateAdd(this.getAlive());
        
        if (this.illness.start && this.getAlive() > 0) {
            // 疾病更新
            this.illness.updateInfected(this.lifeBlock, this.avgContactNum);
            
            // 根据医院情况计算temp传染率
            if (this.isMedicalDeterioration(false)) { // 医院恶化才会发生传染
                this.illness.updateInfected(this.hospitalBlock, 10);
            }
            
            this.illness.updateIncubation(this.lifeBlock);
            this.illness.updateIncubation(this.hospitalBlock);
            this.illness.updateHealing(this.lifeBlock);
            
            // 根据医院情况计算temp治愈率
            let tempHealing = -1;
            if (!this.isMedicalDeterioration(false)) {
                tempHealing = this.WeightingResearchProgress(true, this.illness.getHealing());
            }
            this.illness.updateHealing(this.hospitalBlock, tempHealing, this.lifeBlock);
            
            this.illness.updateDeath(this.lifeBlock);
            this.illness.updateDeath(this.hospitalBlock);
            
            // 疾病变异
            this.illness.variation();
            
            // 结算工作人员损失
            this.settlementWorkersLoss();
            
            // 门诊诊断
            this.treatOutpatient();
            
            // 结算知情度
            const newKnow = ((this.lifeBlock.getMorbidity() + this.hospitalBlock.getMorbidity()) / 2) * 100;
            if (newKnow > 0.2) {
                this.know = newKnow;
            }
            if (this.know > 1) {
                this.know = 1;
            }
            
            // 结算恐慌
            this.panicSettlement();
            
            // 结算生产效率
            this.productionRateSettlement();
        }
        
        // 人口流动
        this.spreadingOut();
    }

    // 人口流动
    spreadingOut() {
        // 通过规模算可达城市数、流出人口数
        const trainNum = [10 * 200, 2 * 200, 2 * 200, 2 * 100, 1 * 100, 1 * 40];
        const levelSub = this.level - 1;

        const transport = (starting, destination, peopleNum) => {
            const actualPeopleNum = Math.min(peopleNum, starting.lifeBlock.getAlive());
            const b = starting.lifeBlock.genOutputBlock(actualPeopleNum, 0, starting.generalExternalHub.isCheck());
            destination.inputBlock(b, starting.illness);
        };

        const randTransport = (peopleNum) => {
            const citySub = Help.randInt(0, this.spreadRange - 1);
            if (citySub >= 0 && citySub < this.allDist.length) {
                const destination = this.allDist[citySub].c;
                if (destination.generalExternalHub.isUsable()) {
                    transport(this, destination, peopleNum);
                    if (this.lifeBlock.getAlive() < this.permanentPopulation) {
                        transport(destination, this, peopleNum);
                    }
                }
            }
        };

        if (this.generalExternalHub.isUsable()) {
            const numTrains = Math.min(trainNum[levelSub], 1000); // 限制最大车次，防止性能问题
            for (let i = 0; i < numTrains; i++) {
                const peopleNum = Help.randInt(0, 600);
                randTransport(peopleNum);
            }
        }

        for (const h of this.allHub) {
            if (h.isUsable()) {
                let peopleNum;
                let numTrains;
                
                if (h.isAirport) {
                    peopleNum = Help.randInt(0, 350);
                    numTrains = 700;
                } else {
                    peopleNum = Help.randInt(0, 600);
                    numTrains = 250;
                }

                const actualNumTrains = Math.min(numTrains, 500); // 限制最大车次，防止性能问题
                for (let i = 0; i < actualNumTrains; i++) {
                    randTransport(peopleNum);
                }
            }
        }
    }

    // 接收人口区块
    inputBlock(b, i) {
        this.lifeBlock.add(b);
        if (b.getInfected() !== 0) {
            this.illness.fuse(i);
        }
    }

    // 加权研究进度
    WeightingResearchProgress(bigger, v, randRange = 0) {
        let result;
        if (bigger) {
            result = (1 + this.researchProgress) * v;
        } else {
            result = (1 - this.researchProgress) * v;
        }
        
        if (randRange > 0) {
            result = Help.randJump(result, randRange);
        }
        
        if (result > 1) {
            return 1;
        } else {
            return Math.max(0, result); // 确保不会小于0
        }
    }

    // 获取剩余床位
    getRemainingBeds() {
        let maxBed = Math.floor(this.medicalNum.workable / this.patientTreatRate);
        const alive = this.hospitalBlock.getAlive(); //医院里每个活人都占用床位
        if (alive >= maxBed) {
            return 0;
        } else {
            return maxBed - alive;
        }
    }

    // 恐慌结算
    panicSettlement() {
        const alive = this.getAlive();
        let avgDeathRate;
        
        if (alive === 0) {
            avgDeathRate = 1;
        } else {
            avgDeathRate = (this.lifeBlock.getDeath() + this.hospitalBlock.getDeath()) / alive;
        }
        
        let panicBase = avgDeathRate * 100;
        if (this.panic < panicBase) {
            this.panic = panicBase;
        }
        
        if (this.isMedicalDeterioration()) {
            this.panic += 0.05;
        }
        
        if (!this.livingMaterial.normal) {
            this.panic += 0.1;
        }
        
        if (this.securityNum.workable < this.securityNum.total * 0.4) {
            if (this.securityNum.workable < this.securityNum.total * 0.2) {
                this.panic += 0.4;
            } else {
                this.panic += 0.2;
            }
        }
        
        if (this.panic > 1) {
            this.panic = 1;
        }
    }

    // 生产效率结算
    productionRateSettlement() {
        const newRate = this.productionEfficiency[this.getOrderStat()];
        this.livingMaterial.productionRate = newRate;
        this.medicalMaterial.productionRate = newRate;
        this.protectiveMaterial.productionRate = newRate;
    }
}

export { City, WorkerGroup, createDistStru, Hub, HubStatus, OutpatientStat };